from playwright.sync_api import Playwright,Browser,Page,Locator,sync_playwright
from playwright.sync_api import TimeoutError as PlaywrightTimeout
from collections import defaultdict
from urllib.parse import urlencode
from tool import datetime,close_SunBrowser,find_chrome_path,timestamp_to_sql_datetime,human_wait,send_email_qq,clean_html
from tool import read_xls_with_merged_cells,remove_all_punct,is_pname_contain_klist
from config import *
from county_config import *
from sql import sqlseverDB
import pandas as pd
import requests,logging,time,random,string,io,math
import re,json,uuid,subprocess,traceback,dirtyjson


tms = sqlseverDB('{SQL Server}', SQL_SERVER, 'SuMaiTongPol', SQL_USER, SQL_PWD)

class LoginError(Exception):
    pass

def read_xls_to_dict_list(file_path,dtype=None):

    if isinstance(file_path,str):
        df = pd.read_excel(file_path,dtype=dtype)
    else:
        with io.BytesIO(file_path) as buffer:
            df = pd.read_excel(buffer, dtype=dtype)
    df = df.map(lambda x: None if pd.isna(x) else x)
    return df.to_dict(orient='records') 

def playwright_connect_browser(basic_info,task_id) ->tuple[Playwright,Browser]:

    rw_name=Task_Name_dict[task_id]
    bid=basic_info['BrowserID']
    bname=basic_info['DpName']
    gname=basic_info['GroupName']
    ping_tai=basic_info['PingTai']
    params={
        'k':ADSPower_API_KEY,
        'user_id':bid,
        'open_tabs':1,
        'ip_tab':0
    }
    while True:
        now_time=datetime.datetime.now()
        err_zds=['iscl','task_id','task_name','BID','loginTime','pintai','bname','gname','des']
        err_params=[0,task_id,rw_name,bid,now_time,ping_tai,bname,gname]

        try:    
            res = requests.get(f'{ADSPower_API_URL}/api/v1/browser/start',params=params,timeout=180)
            res_data = res.json()
            if res_data['code']==0:
                browser=None
                playwright=None
                try:
                    ws_url = res_data['data']['ws']['puppeteer']
                    playwright = sync_playwright().start()

                    # 注意：这里使用connect()而非connect_over_cdp()
                    browser = playwright.chromium.connect_over_cdp(ws_url,timeout=60000)
                    aff=0
                    if task_id not in range(7,13) and task_id!=3 and task_id!=10:
                        start_zds=['bid','task_id','task_name','bname','gname','is_on','state']
                        start_params=[bid,task_id,rw_name,bname,gname,1,0]
                        aff=tms.merge_into('taskStatus',start_zds,start_params,['bid','task_id'],True)
                    logging.info(f'店铺:《{bname}》,{bid},启动指纹浏览器成功,登记任务:{aff}')


                    rs= tms.ExecQuery('select count(*) from taskstatus where bid=? and task_id!=? and is_on=1',(bid,task_id))
                    if rs[0][0]==0:
                        #print(len(browser.contexts))
                        ps=browser.contexts[0].pages
                        logging.info(f'店铺:《{bname}》,{bid},无其他任务执行,关闭无关页面{len(ps)}个')
                        for pp in ps[1:]:
                            pp.close()

                    return playwright,browser
                except Exception as e:
                    traceback.print_exc()
                    if playwright is not None:
                        playwright.stop()
                    logging.info(f'店铺:《{bname}》,{bid},接管浏览器错误 => {e}')
                    close_SunBrowser(bid,task_id)
                    return               

            else:
                err_msg=res_data.get('msg')
                if  err_msg=='Too many request per second, please check':
                    wait_sleep_second=random.randint(3,8)
                    logging.info(f'{bid},启动浏览器过多,等待 {wait_sleep_second} 秒,重试...')
                    time.sleep(wait_sleep_second)
                else:
                    err_params.append(err_msg)
                    aff1=tms.merge_into('ErrBid',err_zds,err_params,['bid'])
                    start_zds=['bid','task_id','task_name','bname','gname','is_on','state']
                    start_params=[bid,task_id,rw_name,bname,gname,0,0]
                    aff2=tms.merge_into('taskStatus',start_zds,start_params,['bid','task_id'],True)
                    logging.error(f"店铺:《{bname}》,{bid},启动指纹浏览器错误:{err_msg},错误状态:{aff1},任务状态:{aff2}")
                    close_SunBrowser(bid,task_id)
                    return
            time.sleep(1)
        except Exception as e:
            logging.error(f'店铺:《{bname}》,{bid},接口超时错误 => {e}')
            close_SunBrowser(bid,task_id)
            return

def playwright_openChrome(dlip=None) ->tuple[Playwright,Browser]:
    flag=False
    try:
        session=requests.session()
        res=session.get('http://localhost:9222/json/version')
        if res.status_code==200 and res.json():
            flag=True
    except Exception as e:
        print('未启动浏览器等待启动')

    try:
        if not flag:
            exepath=find_chrome_path()
            print(exepath)
            if not exepath:
                return None
            dlstr='' if not dlip else f' --proxy-server=http://{dlip}'
            subprocess.Popen(f'"{exepath}" --remote-debugging-port=9222{dlstr}',shell=True, stdin=subprocess.PIPE,
            stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        res=session.get('http://localhost:9222/json/version')
        bobj=res.json()
        ws_url=bobj['webSocketDebuggerUrl']
        
        playwright = sync_playwright().start()
        browser = playwright.chromium.connect_over_cdp(ws_url,timeout=60000)
        return playwright,browser

    except Exception as e:
        traceback.print_exc()
        logging.error(f'play启动浏览器失败 => {e}')

def playwright_make_request(page:Page, url, params=None, data=None):

    if params:
        url = f"{url}?{urlencode(params)}"
    for _ in range(3):
        try:
            if data is None:
                request_script = """
                async function(url) {
                    const response = await fetch(url, {
                        method: 'GET',
                        headers: {
                            'Content-Type': 'application/json'
                        }
                    });
                    const data = await response.json(); 
                    return data; 
                }
                """
                response_data = page.evaluate(request_script, url)
            else:
                request_script = """
                async function(purl, postData) {
                    const response = await fetch(purl, {
                        method: 'POST',
                        headers: {
                            'Content-Type': 'application/json'
                        },
                        body: JSON.stringify(postData)
                    });
                    const data = await response.json();
                    return data; 
                }
                """
                response_data = page.evaluate(request_script, url, data)
            
            return response_data
        except Exception as e:
            logging.error(f'js异步抓包错误 => 《{url}》,{e}')
        time.sleep(3)


def playwright_make_request_native(page: Page, url, data=None, method='POST', headers=None):
    headers = headers or {
        'Accept': 'application/vnd.allegro.internal.v2+json',
        'Content-Type': 'application/vnd.allegro.internal.v2+json',
        'Accept-Encoding': 'gzip, deflate, br, zstd',
        'Accept-Language': 'en-US'
    }



    with page.expect_response(lambda response: response.url.startswith(url) and response.status == 200) as resp_info:
    # 执行 JS 请求

        page.evaluate(f"""
            fetch("{url}", {{
                method: "{method}",
                headers: {json.dumps(headers)},
                body: {json.dumps(json.dumps(data)) if data else 'null'}
            }});
        """)

    resp=resp_info.value
    return resp.json()


def playwright_make_request_for1688(page: Page, url, data=None, method='POST', headers=None):



    with page.expect_response(lambda response: response.url.startswith(url)) as resp_info:
    # 执行 JS 请求

        page.evaluate(f"""
            fetch("{url}", {{
                method: "{method}"
            }});
        """)

    resp=resp_info.value
    return resp.text()

pat_phlm=re.compile(r'cat(?:\.\d+)*\.(\d+)')
def playwright_get_ph_plist(page:Page,basic_info,lm_url,price_range=None):
    
    bid=basic_info['BrowserID']
    dpName=basic_info['DpName']
    mat_lmid=pat_phlm.search(lm_url)
    if not mat_lmid:
        return
    lmid=mat_lmid.group(1)
    url='https://shopee.ph/api/v4/search/search_items'
    price_min=800
    price_max=3000
    if price_range:
        price_min,price_max=price_range
    newest=0
    max_pcount=420
    pp=1
    pdatas=[]
    err_count=0
    while True:
        random_uuid = str(uuid.uuid4())
        if newest>=max_pcount:
            break
        params = {
            'by': 'sales',
            'limit': 60,
            'locations': 'Overseas',
            'match_id': lmid,
            'newest': newest,
            'order': 'desc',
            'page_type': 'search',
            'price_max': price_max,
            'price_min': price_min,
            'rating_filter': 4,
            'scenario': 'PAGE_CATEGORY',
            'shippings': 5,
            'source': 'SRP',
            'version': 2,
            'view_session_id': random_uuid
        }

        js_data=playwright_make_request(page,url,params)
        if js_data:
            #print(js_data)
            if not js_data['error'] and js_data.get('items'):
                if newest==0:
                    max_pcount=js_data['total_count']
                    logging.info(f'店铺:《{dpName}》,{bid},类目:{lmid},获取类目产品总数量:{max_pcount}')
                with open(f'JSONDATA/菲律宾类目{lmid}_{newest}.json','w',encoding='utf-8') as f:
                    json.dump(js_data,f,ensure_ascii=False,indent=4)
                pitems=js_data['items']
                pdatas.extend(pitems)
                logging.info(f'店铺:《{dpName}》,{bid},类目:{lmid},第{pp}页，获取产品总数量:{len(pitems)},当前总获取:{len(pdatas)}')
                newest+=60
                pp+=1
            else:
                print(f'店铺:《{dpName}》,{bid},类目:{lmid},第{pp}页,出错 => {js_data}')
                page.goto(lm_url)
                sj_sec=1 + random.random() * 2
                time.sleep(sj_sec)
                err_count+=1
        else:
            print(f'店铺:《{dpName}》,{bid},类目:{lmid},第{pp}页,出错js接口请求出错')
            page.goto(lm_url)
            sj_sec=1 + random.random() * 4
            time.sleep(sj_sec)
            err_count+=1
        
        sj_sec=1 + random.random() * 4
        time.sleep(sj_sec)
    
    return pdatas       


def check_login(page:Page,basic_info,acc_xpath='xpath=//div[@class="subaccount-info"]'):
    bid=basic_info['BrowserID']
    dpName=basic_info['DpName']
    userName=basic_info['UserName']
    password=basic_info['Password']
    basic_url=basic_info['url']
    wait_count=0
    no_login_count=0

    while True:

        if MAX_WAIT and wait_count>MAX_WAIT:
            return
        try:
            acc=page.locator(acc_xpath)
            no_login=page.locator('xpath=//input[@name="loginKey"]')
            if no_login.count()>0:
                if no_login_count>=3:
                    return
                
                logging.info(f'店铺:《{dpName}》,{bid},账号未登录,进行登录')

                if not userName or not password:
                    return
                # el_zh=page.locator('input[name="loginKey"]')
                # el_mb=page.locator('input[name="password"]')
                # if not (el_zh.input_value() or el_mb.input_value()):
                page.fill('input[name="loginKey"]',userName)
                page.fill('input[name="password"]',password)
                try:
                    with page.expect_navigation():  # 同步 API 使用上下文管理器
                        btn_login=page.locator('xpath=//form/button[1]')
                        try:
                            btn_login.click(delay=200)
                        except Exception as e:
                            return
                except TimeoutError:
                    acc0=page.locator(acc_xpath)
                    if acc0.count()>0:
                        logging.info(f'店铺:《{dpName}》,{bid},已登录acc0')
                        return page
                    else:
                        logging.info(f'店铺:《{dpName}》,{bid},登录超时错误')
                        return
                except PlaywrightTimeout:
                    acc0=page.locator(acc_xpath)
                    if acc0.count()>0:
                        logging.info(f'店铺:《{dpName}》,{bid},已登录acc0')
                        return page
                    else:
                        logging.info(f'店铺:《{dpName}》,{bid},登录超时错误')
                        return
                    
                #logging.info(f"店铺:{dpName},{bid},当前URL:{page.url}")
                if 'verify/captcha' in page.url or 'verify/traffic/error' in page.url:
                    logging.info(f'店铺:{dpName},{bid},导航到错误页面退出')
                    sj_sce=1+random.random()*4
                    time.sleep(sj_sce)
                    raise LoginError
                    
                no_login_count+=1
                
            if acc.count()>0:
                logging.info(f'店铺:《{dpName}》,{bid},已登录acc')
                return page
            
            if 'verify/captcha' in page.url or 'verify/traffic/error' in page.url:
                logging.info(f'店铺:{dpName},{bid},301到了错误页面')
                sj_sce=1+random.random()*4
                time.sleep(sj_sce)
                raise LoginError
                

            wait_count+=1
        except LoginError:
            raise LoginError(f"店铺:{dpName},{bid},301到了错误页面")
        except Exception as e:
            #traceback.print_exc()
            logging.info(f'店铺:《{dpName}》,{bid},出错 => {e}')
            wait_count+=1
        time.sleep(1)


def wait_goto_response(page:Page,basic_info,url,api_url: str, timeout: float = 60):
    bid=basic_info['BrowserID']
    dpName=basic_info['DpName']

    def tppp(res):
        if api_url in res.url and res.status == 200:
            try:
                jsdata=res.json()
                if jsdata['error']:
                    if jsdata['error']==266900002:
                        return True
                    return False
                else:
                    return True
            except:
                return False
        else:
            return False
    cmt_list=None
    cmt_max_p=3
    try:
        if '/product/' in url:
            wait_pl=page.expect_event("response",lambda res: "/v2/item/get_ratings" in res.url and res.status == 200,timeout=timeout*1000)

        with page.expect_event("response", lambda res :tppp(res),timeout=timeout*1000) as resp_info:
            page.goto(url,timeout=80000)
            ttt=check_login(page,basic_info,'xpath=//div[@class="navbar__username"]')
            if not ttt:
                logging.info("登录失败，主动关闭页面")
                raise LoginError

        res = resp_info.value
        js_data=res.json()
        if js_data['error']==266900002:
            return {'code':404,'msg':'页面404'}
        
        if '/product/' in url:
            try:
                with wait_pl as resp_info2:
                    res2=resp_info2.value
                    js_data2=res2.json()
                    cmt_list=js_data2['data']['ratings']
                    cmt_total=js_data2['data']['item_rating_summary']['rating_total']
                    if len(cmt_list)>0:
                        cmt_max_p=math.ceil(cmt_total/len(cmt_list))
                    logging.info(f"店铺:《{dpName}》,{bid},首页评论 {len(cmt_list)} 个,成功截取")
            except Exception as e:
                traceback.print_exc()
                logging.info(f"店铺:《{dpName}》,{bid},截取评论错误 = > {e}")
        if cmt_max_p>3:
            cmt_max_p=3
        return {'code':0,'data':js_data,'cmt_list':cmt_list,'cmt_max_p':cmt_max_p}

    except LoginError as le:
        logging.info(f"店铺:《{dpName}》,{bid},登录失败：{le}")
        return {'code': -2, 'msg': str(le)}

    except PlaywrightTimeout as te:
        logging.info(f"店铺:《{dpName}》,{bid},接口超时：{te}")
        return {'code': -3, 'msg': '接口超时'}

    except Exception as e:
        traceback.print_exc()
        logging.info(f'店铺:《{dpName}》,{bid},未知接口错误 => {e}')
        return {'code': -4, 'msg': '接口错误'}


def wait_click_response(page:Page,basic_info,el:Locator,api_url: str, timeout: float = 120):
    bid=basic_info['BrowserID']
    dpName=basic_info['DpName']
    def tppp(res):
        if api_url in res.url and res.status == 200:
            try:
                jsdata=res.json()
                if jsdata['error']:
                    return False
                else:
                    return True
            except:
                return False
        else:
            return False
    try:
        with page.expect_event("response", lambda res :tppp(res),timeout=timeout*1000) as resp_info:
            el.click(delay=200)
            ttt=check_login(page,basic_info,'xpath=//div[@class="navbar__username"]')
            if not ttt:
                logging.info("登录失败，主动关闭页面")
                raise LoginError
        res = resp_info.value 
        js_data=res.json()
        return {'code':0,'data':js_data}
    

    except LoginError as le:
        logging.info(f"店铺:《{dpName}》,{bid},登录失败：{le}")
        return {'code': -2, 'msg': str(le)}

    except PlaywrightTimeout as te:
        logging.info(f"店铺:《{dpName}》,{bid},接口超时：{te}")
        return {'code': -3, 'msg': '接口超时'}

    except Exception as e:
        traceback.print_exc()
        logging.info(f'店铺:《{dpName}》,{bid},未知接口错误 => {e}')
        return {'code': -4, 'msg': '接口错误'}

def ask_seller(page:Page,basic_info=None):

    api_url='https://edge.allegro.pl/message-center/facade/platform-message'
    data={"inputValues":{"message":"请问测试准吗?"},"offerId":"17265391433","questionType":"OFFER"}

    res=playwright_make_request_native(page,api_url,data=data)
    print(res)


# 生成随机SKU ID
def generate_random_code(length=8):
    characters = string.ascii_uppercase + string.digits
    rcode = ''.join(random.choices(characters, k=length))
    return rcode

def get_int_price(ppp):
    if ppp is None or ppp==-1:
        return ppp
    else:
        return int(ppp/100000)
    
def save_ph_pro(pdatas,lm_item,country):


    def get_sjm():
        while True:
            sjm=generate_random_code()
            rs_sjm=tms.ExecQuery('select count(*) from ShopeePHADPro where random_code=?',(sjm,))
            if rs_sjm[0][0]==0:
                return sjm

    suc=0
    fail=0
    ig_n=0

    zzid=lm_item['id']
    lm_url=lm_item['lmurl']
    #match_id=lm_item['match_id']
    kw=lm_item['keyword']
    keyword_type=lm_item['keyword_type']
    gender=lm_item['gender']
    
    now_time=datetime.datetime.now()

    pj=1
    no_shop_location_count=0
    no_sold_count=0
    no_cmt_count=0
    official_shop_count=0
    for pdata in pdatas:
        item_basic=pdata['item_basic']
        if item_basic:
            
            is_official_shop=item_basic.get('is_official_shop')
            if is_official_shop:
                official_shop_count+=1
                pj+=1
                continue
            shopid=str(item_basic['shopid'])
            itemid=str(item_basic['itemid'])
            ctime=item_basic['ctime']
            sql_ctime=timestamp_to_sql_datetime(ctime)
            imgs=item_basic.get('images')
            if imgs:
                imgs=[f"{IMG_HOST_MAP[country]}{img}" for img in imgs]
                imgs=json.dumps(imgs)
            vlist=item_basic.get('video_info_list')
            if vlist:
                for vvv in vlist:
                    if vvv.get('thumb_url'):
                        vvv['thumb_url']=f"{IMG_HOST_MAP[country]}{vvv['thumb_url']}"
                vlist=json.dumps(vlist)

            try:
                shop_location=item_basic.get('shop_location')
                if shop_location!='Mainland China':
                    no_shop_location_count+=1
                    pj+=1
                    continue
                m_sold=item_basic.get('sold')
                if m_sold<5 or m_sold>15:
                    no_sold_count+=1
                    pj+=1
                    continue
                
                if item_basic.get('cmt_count',0)<2:
                    no_cmt_count+=1
                    pj+=1
                    continue
                pinfo={
                    'url':f'{Country_Host_Map[country]}/product/{shopid}/{itemid}/',
                    'itemid':itemid,
                    'shopid':shopid,
                    'shop_name':item_basic.get('shop_name'),
                    'pname':item_basic.get('name'),
                    'random_code':get_sjm(),
                    'catid':item_basic.get('catid'),
                    #'match_id':match_id,
                    'image':f"{IMG_HOST_MAP[country]}{item_basic.get('image')}",
                    'images':imgs,
                    'video_info_list':vlist,
                    'tier_variations':json.dumps(item_basic.get('tier_variations'),ensure_ascii=False) if item_basic.get('tier_variations') else None,
                    'currency':item_basic.get('currency'),
                    'price':get_int_price(item_basic.get('price')),
                    'price_min':get_int_price(item_basic.get('price_min')),
                    'price_max':get_int_price(item_basic.get('price_max')),
                    'price_before_discount':get_int_price(item_basic.get('price_before_discount')),
                    'price_min_before_discount':get_int_price(item_basic.get('price_min_before_discount')),
                    'price_max_before_discount':get_int_price(item_basic.get('price_max_before_discount')),
                    'shop_location':shop_location,
                    'liked_count':item_basic.get('liked_count'),
                    'month_sold':m_sold,
                    'historical_sold':item_basic.get('historical_sold'),
                    'rating_star':round(item_basic['item_rating']['rating_star'],2),
                    'cmt_count':item_basic.get('cmt_count'),
                    'ctime':sql_ctime,
                    'from_lmid':zzid,
                    'from_lmurl':lm_url,
                    'from_keyword':kw,
                    'keyword_type':keyword_type,
                    'gender':gender,
                    'country':country,
                    #'json_data':json.dumps(pdata,ensure_ascii=False),
                    'update_time':now_time,
                    'upload_time':now_time,
                    'pbrand':item_basic.get('brand'),
                    'json_type':'item_basic'
                }

                #获取随机码

                rs=tms.ExecQuery(f'select count(*) from ShopeePHADPro where itemid=?',(itemid,))
                if rs[0][0]>0:
                    #logging.info(f'类目:{match_id},第{pj}个itemid:{itemid},已存在数据表忽略')
                    ig_n+=1
                else:
        
                    zdstr=','.join(list(pinfo.keys()))
                    paras=list(pinfo.values())
                    wstr = ','.join('?' for _ in paras)
                    sql_str=f'insert into ShopeePHADPro({zdstr}) values ({wstr})'
                    aff=tms.ExecNoQuery(sql_str,paras)
                    if aff:
                        suc+=1
                    else:
                        aaa={k:len(v) for k,v in pinfo.items() if isinstance(v,str)}
                        print(aaa)
                        fail+=1

            except Exception as e:
                traceback.print_exc()
                logging.error(f'json解析错误 =>{e}')
        
        else:
            item_data=pdata['item_data']
            item_card=pdata['item_card_displayed_asset']
            shopid=str(item_data['shopid'])
            itemid=str(item_data['itemid'])
            ctime=item_data['ctime']
            sql_ctime=timestamp_to_sql_datetime(ctime)
            imgs=item_card.get('images')
            if imgs:
                imgs=[f"{IMG_HOST_MAP[country]}{img}" for img in imgs]
                imgs=json.dumps(imgs)
            vlist=item_card.get('highlight_videos')
            if vlist:
                vlist=json.dumps(vlist)
            shop_location=item_card.get('shop_location')
 
            try:
                if shop_location!='Mainland China':
                    no_shop_location_count+=1
                    pj+=1
                    continue
                m_sold=item_data['item_card_display_sold_count'].get('monthly_sold_count')
                if m_sold<5 or m_sold>15:
                    no_sold_count+=1
                    pj+=1
                    continue
                pinfo={
                    'url':f'https://shopee.ph/product/{shopid}/{itemid}/',
                    'itemid':itemid,
                    'shopid':shopid,
                    'shop_name':item_data['shop_data'].get('shop_name'),
                    'pname':item_card.get('name'),
                    'random_code':get_sjm(),
                    'catid':item_data.get('catid'),
                    #'match_id':match_id,
                    'image':f"{IMG_HOST_MAP[country]}{item_card.get('image')}",
                    'images':imgs,
                    'video_info_list':vlist,
                    'tier_variations':json.dumps(item_data.get('tier_variations'),ensure_ascii=False) if item_data.get('tier_variations') else None,
                    'currency':'PHP',
                    'price':get_int_price(item_data['item_card_display_price']['price']),
                    'shop_location':item_card.get('shop_location'),
                    'liked_count':item_data.get('liked_count'),
                    'month_sold':item_data['item_card_display_sold_count'].get('monthly_sold_count'),
                    'historical_sold':item_data['item_card_display_sold_count'].get('historical_sold_count'),
                    'rating_star':round(item_data['item_rating']['rating_star'],2),
                    'ctime':sql_ctime,
                    'from_lmid':zzid,
                    'from_lmurl':lm_url,
                    'from_keyword':kw,
                    'keyword_type':keyword_type,
                    'gender':gender,
                    'country':country,
                    #'json_data':json.dumps(pdata,ensure_ascii=False),
                    'pbrand':item_data['global_brand']['display_name'],
                    'json_type':'item_data',
                    'update_time':now_time,
                    'upload_time':now_time
                }

                rs=tms.ExecQuery(f'select count(*) from ShopeePHADPro where itemid=?',(itemid,))
                if rs[0][0]>0:
                    #logging.info(f'类目:{match_id},第{pj}个itemid:{itemid},已存在数据表忽略')
                    ig_n+=1
                else:
        
                    zdstr=','.join(list(pinfo.keys()))
                    paras=list(pinfo.values())
                    wstr = ','.join('?' for _ in paras)
                    sql_str=f'insert into ShopeePHADPro({zdstr}) values ({wstr})'
                    aff=tms.ExecNoQuery(sql_str,paras)
                    if aff:
                        suc+=1
                    else:
                        aaa={k:len(v) for k,v in pinfo.items() if isinstance(v,str)}
                        print(aaa)
                        fail+=1

            except Exception as e:
                traceback.print_exc()
                logging.error(f'json解析错误 =>{e}')
        
        pj+=1
    
    logging.info(f'关键词:《{kw}》,保存状态 => 成功:{suc},失败:{fail},忽略:{ig_n},非Mainland China:{no_shop_location_count},评论小于2:{no_cmt_count},销量不在5~15:{no_sold_count},Mall:{official_shop_count}')
    return suc

def save_ph_dplist(pdatas,lm_item):

    def get_sjm():
        while True:
            sjm=generate_random_code()
            rs_sjm=tms.ExecQuery('select count(*) from ShopeePHADPro where random_code=?',(sjm,))
            if rs_sjm[0][0]==0:
                return sjm
    suc=0
    fail=0
    ig_n=0

    zzid=lm_item['id']
    lm_url=lm_item['ysdpurl']
    now_time=datetime.datetime.now()

    pj=1
    for pdata in pdatas:

        shopid=str(pdata['shopid'])
        itemid=str(pdata['itemid'])
        ctime=pdata['ctime']
        sql_ctime=timestamp_to_sql_datetime(ctime)
        item_card_displayed_asset=pdata['item_card_displayed_asset']
        item_card_display_price=pdata['item_card_display_price']
        item_card_display_sold_count=pdata['item_card_display_sold_count']
        imgs=item_card_displayed_asset.get('images')
        if imgs:
            imgs=[f"https://down-ph.img.susercontent.com/file/{img}" for img in imgs]
            imgs=json.dumps(imgs)

        try:
            pinfo={
                'url':f'https://shopee.ph/product/{shopid}/{itemid}/',
                'itemid':itemid,
                'shopid':shopid,
                'shop_name':pdata['shop_data']['shop_name'],
                'pname':item_card_displayed_asset.get('name'),
                'random_code':get_sjm(),
                'catid':pdata.get('catid'),
                'image':f"https://down-ph.img.susercontent.com/file/{item_card_displayed_asset.get('image')}",
                'images':imgs,
                'tier_variations':json.dumps(pdata.get('tier_variations'),ensure_ascii=False) if pdata.get('tier_variations') else None,
                'currency':'PHP',
                'price':get_int_price(item_card_display_price.get('price')),
                'shop_location':pdata['shop_data']['shop_location'],
                'liked_count':pdata.get('liked_count'),
                'month_sold':item_card_display_sold_count.get('monthly_sold_count'),
                'historical_sold':item_card_display_sold_count.get('historical_sold_count'),
                'rating_star':round(pdata['item_rating']['rating_star'],2),
                'ctime':sql_ctime,
                'from_lmid':zzid,
                'from_dpurl':lm_url,
                'json_data':json.dumps(pdata,ensure_ascii=False),
                'update_time':now_time,
                'upload_time':now_time,
                'json_type':'item_card'
            }

            #获取随机码

            rs=tms.ExecQuery(f'select count(*) from ShopeePHADPro where itemid=?',(itemid,))
            if rs[0][0]>0:
                #logging.info(f'类目:{match_id},第{pj}个itemid:{itemid},已存在数据表忽略')
                ig_n+=1
            else:
    
                zdstr=','.join(list(pinfo.keys()))
                paras=list(pinfo.values())
                wstr = ','.join('?' for _ in paras)
                sql_str=f'insert into ShopeePHADPro({zdstr}) values ({wstr})'
                aff=tms.ExecNoQuery(sql_str,paras)
                if aff:
                    suc+=1
                else:
                    aaa={k:len(v) for k,v in pinfo.items() if isinstance(v,str)}
                    print(aaa)
                    fail+=1

        except Exception as e:
            traceback.print_exc()
            logging.error(f'json解析错误 =>{e}')

        pj+=1
    
    logging.info(f'店内采集,保存状态 => 成功:{suc},失败:{fail},忽略:{ig_n}')
    return suc

def save_ph_pro_deail(pdata,pitem,country,cmt):

    pid,purl,itemid=pitem
    is_update_pro=False
    is_update_sku=False
    try:
        pdata_item=pdata['item']
        fe_categories=json.dumps(pdata_item['fe_categories'])
        cmt_count=pdata['product_review']['cmt_count']
        attributes=json.dumps(pdata['product_attributes'])
        tier_variations=pdata_item['tier_variations']
        sku_info={
            'sku1_name':None,
            'sku2_name':None,
            'sku1_options':None,
            'sku2_options':None,
            'sku1_imgs':None,
            'sku2_imgs':None,
        }
        if tier_variations:
            for j in range(1,len(tier_variations)+1):
                vari=tier_variations[j-1]
                sku_info[f'sku{j}_name']=vari['name']
                sku_info[f'sku{j}_options']=vari['options']
                if vari.get('images'):
                    sku_info[f'sku{j}_imgs']=[f'{IMG_HOST_MAP[country]}{img}' for img in vari['images']]
        else:
            sku_info['sku1_name']='style'
        
        pbrand=pdata_item['brand']    
        pdes=pdata_item['description']

        #补全表《ShopeePHADPro》信息
        models=pdata_item['models']
        etra_dict={
            'fe_categories':fe_categories,
            'cmt_jlist':cmt,
            'cmt_count':cmt_count,
            'attributes':attributes,
            'sku1_name':sku_info['sku1_name'],
            'sku2_name':sku_info['sku2_name'],
            'models':json.dumps(models),
            'pbrand':pbrand,
            'pdec':pdes
        }
        zds=list(etra_dict.keys())
        paras=list(etra_dict.values())
        zdstr=','.join([f'{zd}=?' for zd in zds])
        aff_pro=tms.ExecNoQuery(f'update ShopeePHADPro set {zdstr} where id=?',paras+[pid])
        if aff_pro:
            is_update_pro=True
        logging.info(f'itemid:{itemid},补全信息状态:{aff_pro}')
        #补全SKU信息
        pskus=[]
        now_time=datetime.datetime.now()
        aff_sku=0
        if not models:
            models=[]
            logging.info(f'itemid:{itemid},单sku')
            rsp=tms.ExecQuery('select image,month_sold,price from ShopeePHADPro where itemid=?',(itemid,))
            one_img,one_month_sold,one_price=rsp[0]
            psku_dict={
                'itemid':itemid,
                'skuid':itemid,
                'sku_text':'one style',
                'sku1':'one style',
                'sku2':None,
                'sku1_img':one_img,
                'sku2_img':None,
                'sku_price':one_price,
                'sku_sold':one_month_sold,
                'from_pid':pid,
                'update_time':now_time,
                'upload_time':now_time
            }
            zds=list(psku_dict.keys())
            paras=list(psku_dict.values())
            aff_sku+=tms.merge_into('ShopeePHADProSKU',zds,paras,['itemid','skuid'],True)
        else:
            for moddd in models:
                skuid=str(moddd['model_id'])
                sku_price=int(moddd['price']/100000)
                sku_text=moddd['name']
                sku_sold=moddd['sold']
                tier_index=moddd['extinfo']['tier_index']

                skuddd={
                    'sku1':None,
                    'sku2':None,
                    'sku1_img':None,
                    'sku2_img':None
                }
                if tier_index:
                    for tj in range(len(tier_index)):
                        
                        skuddd[f'sku{tj+1}']=sku_info[f'sku{tj+1}_options'][tier_index[tj]]
                        if sku_info[f'sku{tj+1}_imgs']:
                            skuddd[f'sku{tj+1}_img']=sku_info[f'sku{tj+1}_imgs'][tier_index[tj]]
                else:
                    skuddd['sku1']=sku_text
                psku_dict={
                    'itemid':itemid,
                    'skuid':skuid,
                    'sku_text':sku_text,
                    'sku1':skuddd['sku1'],
                    'sku2':skuddd['sku2'],
                    'sku1_img':skuddd['sku1_img'],
                    'sku2_img':skuddd['sku2_img'],
                    'sku_price':sku_price,
                    'sku_sold':sku_sold,
                    'from_pid':pid,
                    'update_time':now_time,
                    'upload_time':now_time
                }
                pskus.append(psku_dict)
                zdstr2=','.join(psku_dict.keys())
                paras2=list(psku_dict.values())
                wstr=','.join(['?' for _ in paras2])
                rs_sku=tms.ExecQuery('select count(*) from ShopeePHADProSKU where skuid=?',(skuid,))
                if rs_sku[0][0]==0:
                    aff_sku+=tms.ExecNoQuery(f'insert into ShopeePHADProSKU({zdstr2}) values({wstr})',paras2)
                else:
                    aff_sku+=1
                    
        logging.info(f'itemid:{itemid},共有{len(models)}个sku,存入 {aff_sku} 个')

        if aff_sku:
            is_update_sku=True
        
        return is_update_pro and is_update_sku
    
    except Exception as e:
        traceback.print_exc()
        logging.error(f'json解析错误 =>{e}')
        return 0

def parse_jsonp(jsonp_str):
    """
    从 JSONP 响应中提取 JSON 并解析成 Python 对象

    参数:
        jsonp_str (str): 形如 callback({...}) 的字符串

    返回:
        dict 或 list: 提取出的 JSON 对象

    抛出:
        ValueError: 如果解析失败
    """
    try:
        match = re.search(r'^[^\(]*\((.*)\)\s*$', jsonp_str, re.DOTALL)
        if not match:
            raise ValueError("Invalid JSONP format")
        json_str = match.group(1)
        return json.loads(json_str)
    except Exception as e:
        raise ValueError(f"Failed to parse JSONP: {e}")
    
def catch_pat(res,api_url):
    acc_url='https://gobuy.1688.com/pc/base/account?'

    # if acc_url in res.url:
    #     if 'userId' not in res.text():
    #         print('未登录账号')
    #         raise LoginError
    

    if api_url in res.url and '_getOfferList' in res.url and res.status == 200:
        try:
            jsonp_data=res.text()
            js_data=parse_jsonp(jsonp_data)
            js_data['data']['data']['OFFER'].get('items')
            return True
        except Exception as e:
            print(res.text())
            return False
        
    
    else:
        return False

def wait_1688_goto_text(page:Page,basic_info,url,api_url: str, timeout: float = 60):
    bid=basic_info['BrowserID']
    dpName=basic_info['DpName']
    cjkw=basic_info['kw']
    gender=basic_info['gender']
   
    for _ in range(2):
        try:
            with page.expect_event("response", lambda res :catch_pat(res,api_url),timeout=timeout*1000) as resp_info:
                page.goto(url,timeout=120000)
                time.sleep(3)
                slider_watcher(page)

            el_tip=page.locator('//div[@class="divider-tips"]')
            if el_tip.count()>0:
                if '没有相关商品，推荐试试搜这些' in el_tip.text_content():
                    logging.info(f'店铺:《{dpName}》,{bid},关键词:《{cjkw}》等级{gender},没有相关产品退出采集')
                    return {'code':0,'data':'没有相关产品'}

            res = resp_info.value
            jsonp_data=res.text()
            js_data=parse_jsonp(jsonp_data)
            return {'code':0,'data':js_data}

        except LoginError as le:
            logging.info(f"店铺:《{dpName}》,{bid},关键词:《{cjkw}》等级{gender},登录失败：{le}")
            return {'code': -2, 'msg': str(le)}

        except PlaywrightTimeout as te:
            logging.info(f"店铺:《{dpName}》,{bid},关键词:《{cjkw}》等级{gender},接口超时：{te}")
            if _==1:
                return {'code': -3, 'msg': '接口超时'}

        except Exception as e:
            #traceback.print_exc()
            logging.info(f'店铺:《{dpName}》,{bid},关键词:《{cjkw}》等级{gender},未知接口错误 => {e}')
            return {'code': -4, 'msg': '接口错误'}

def wait_1688_click_text(page:Page,basic_info,el:Locator,api_url: str, timeout: float = 60):
    bid=basic_info['BrowserID']
    dpName=basic_info['DpName']
    cjkw=basic_info['kw']
    gender=basic_info['gender']
    for _ in range(2):
        try:
            
            with page.expect_event("response", lambda res :catch_pat(res,api_url),timeout=timeout*1000) as resp_info:
                if _==0:
                    el.hover()
                    time.sleep(3)
                    el.click(delay=1000)
                else:
                    print('页面重新加载')
                    page.reload(timeout=180000)
                time.sleep(3)
                slider_watcher(page)
            res = resp_info.value
            jsonp_data=res.text()
            js_data=parse_jsonp(jsonp_data)
            return {'code':0,'data':js_data}

        except LoginError as le:
            logging.info(f"店铺:《{dpName}》,{bid},关键词:《{cjkw}》等级{gender},登录失败：{le}")
            return {'code': -2, 'msg': str(le)}

        except PlaywrightTimeout as te:
            logging.info(f"店铺:《{dpName}》,{bid},关键词:《{cjkw}》等级{gender},接口超时：{te}")
            if _==1:
                return {'code': -3, 'msg': '接口超时'}

        except Exception as e:
            #traceback.print_exc()
            logging.info(f'店铺:《{dpName}》,{bid},关键词:《{cjkw}》等级{gender},未知接口错误 => {e}')
            return {'code': -4, 'msg': '接口错误'}

pat_sale=re.compile(r'.+?(\d+?|\d+?\.\d+?万|\d+万)\+件$')


def get_sjm():
    while True:
        sjm=generate_random_code()
        rs_sjm=tms.ExecQuery('select count(*) from S1688Pro where random_code=?',(sjm,))
        if rs_sjm[0][0]==0:
            return sjm

def save_1688_plist(pdatas,lm_item,tj):

    suc=0
    fail=0
    ig_n=0
    search_kw=lm_item['keyword']
    skw_list=remove_all_punct(search_kw).split()
    rs_qqkw=tms.ExecQuery('select word_blacklist from Blacklist where word_language=?',('中文',))
    qqkw_list=[r[0] for r in rs_qqkw]
    kw_type=lm_item['keyword_type']
    kw_gj=lm_item['emarket_json']
    kw_gender=lm_item['min_gender']
    now_time=datetime.datetime.now()
    pj=1
    no_sale_count=0
    sale_no2000_count=0
    return_rate_count=0
    pf_not_count=0
    price_not_count=0
    shop_name_not_count=0
    no_contain_count=0
    no_yun_fei_count=0
    qq_count=0
    hz_list=tj['shop_name_export'].split('|')

    minp_str,maxp_str=tj['price_export'].split('-')
    minp=int(minp_str)
    maxp=int(maxp_str)

    historical_sold_export=tj['historical_sold_export']
    if historical_sold_export:
        min_sold,max_sold=historical_sold_export.split('-')
        min_sold=int(min_sold)
    min_sold=50
    
    rating_star_export=tj['rating_star_export']
    if rating_star_export:
        min_rating_star,max_rating_star=rating_star_export.split('-')
        min_rating_star=float(min_rating_star)
    
    bookedCount_export=tj['bookedCount_export']
    if bookedCount_export:
        min_bookedCount,max_bookedCount=bookedCount_export.split('-')
        min_bookedCount=int(min_bookedCount)

    for pdata in pdatas:
        try:
            pinfo=pdata['data']
            if not pinfo.get('title'):
                no_contain_count+=1
                pj+=1
                continue
            pname=clean_html(pinfo['title'])
            if kw_gender<4 and (not is_pname_contain_klist(pname,skw_list)):
                no_contain_count+=1
                pj+=1
                continue

            sale_text=None
            if pinfo.get('afterPrice'):
                if pinfo['afterPrice'].get('text'):
                    sale_text=pinfo['afterPrice']['text']

            
            if is_pname_contain_klist(pname,qqkw_list):
                qq_count+=1
                pj+1
                continue

            if sale_text is None:
                no_sale_count+=1
                pj+=1
                continue
            
            cur_historical_sold=None
            mat=pat_sale.match(sale_text)
            sale_num_str=mat.group(1).strip()
            if '万' in sale_num_str:
                
                sale_num=(float(sale_num_str.replace('万','')))*10000
                cur_historical_sold=int(sale_num)
            else:
                cur_historical_sold=int(float(sale_num_str))
   
            
            shopAddition=pinfo.get('shopAddition')
            shop_url=shopAddition.get('shopLinkUrl') if shopAddition else None
            shop_location=shopAddition.get('text') if shopAddition else None
            rating_star = None
            if shopAddition and shopAddition.get('tradeService') and shopAddition.get('tradeService').get('compositeNewScore'):
                rating_star=float(shopAddition['tradeService']['compositeNewScore'])

            
            shopinfo=pinfo['shop']
            priceInfo=pinfo['priceInfo']
            cur_price=float(priceInfo['price'])
            itemid=pinfo['offerId']
            return_rate=0
            ctags=pinfo['tags']
            tags_txt='|'.join([ctag.get('text') for ctag in ctags if ctag.get('text')])
            is_yun_fei_xian=False
            for ctag in ctags:
                if ctag.get('matKey')=='return_rate':
                    return_rate_text=ctag.get('text')
                    if return_rate_text:
                        num_mat=re.search(r'回头率(\d+?)%',return_rate_text)
                        if num_mat:
                            return_rate=float(num_mat.group(1))/100
                            return_rate_count+=1
                if ctag.get('text')=='退货包运费':
                    is_yun_fei_xian=True
            
            if not is_yun_fei_xian:
                no_yun_fei_count+=1
                pj+=1
                continue

            
            is_contain_hz=False
            for hz in hz_list:
                if hz in shopinfo['text']:
                    is_contain_hz=True
                    break
            if not is_contain_hz:
                shop_name_not_count+=1
                pj+=1
                continue
                         
            if cur_historical_sold<min_sold:
                sale_no2000_count+=1
                pj+=1
                continue

            if cur_price>maxp or cur_price<minp:
                price_not_count+=1
                pj+=1
                continue
            
            if not rating_star or (rating_star and rating_star<min_rating_star):
                pf_not_count+=1
                pj+=1
                continue

            pdict={
                'url':f'https://detail.1688.com/offer/{itemid}.html',
                'itemid':itemid,
                'shopid':pinfo['memberId'],
                'shop_name':shopinfo['text'],
                'shop_url':shop_url,
                'pname':pname,
                'random_code':get_sjm(),
                'image':pinfo['odPicUrl'],
                'currency':'CNY',
                'price':cur_price,
                'shop_location':shop_location,
                'bookedCount':pinfo['bookedCount'],
                'shop_year':int(shopinfo['tpYear']),
                'rating_star':rating_star,
                'sku1_name':'color',
                'sku2_name':'size',
                'pbrand':pinfo['imallBrand'],
                'from_keywords':search_kw,
                'json_type':'OFFER',
                'update_time':now_time,
                'upload_time':now_time,
                'sale_text':sale_text,
                'return_rate':return_rate,
                'historical_sold':cur_historical_sold,
                'emarket_json':kw_gj,
                'keyword_type':Keyword_Type_Gender_Map[kw_gender],
                'keword_gender':kw_gender,
                'ship_tag':tags_txt
            }


            rs=tms.ExecQuery(f'select count(*) from S1688Pro where itemid=?',(itemid,))
            if rs[0][0]>0:
                #logging.info(f'类目:{match_id},第{pj}个itemid:{itemid},已存在数据表忽略')
                ig_n+=1
            else:
    
                zdstr=','.join(list(pdict.keys()))
                paras=list(pdict.values())
                wstr = ','.join('?' for _ in paras)
                sql_str=f'insert into S1688Pro({zdstr}) values ({wstr})'
                aff=tms.ExecNoQuery(sql_str,paras)
                if aff:
                    suc+=1
                else:
                    aaa={k:len(v) for k,v in pdict.items() if isinstance(v,str)}
                    print(aaa)
                    fail+=1
            if suc>=20:
                break

        except Exception as e:
            traceback.print_exc()
            logging.error(f'json解析错误 =>{e}')
            fail+=1
            
        pj+=1
    logging.info(f'搜索词:《{search_kw}》,共采集 {len(pdatas)} 个,保存成功:{suc},保存失败:{fail}\n有回头率:{return_rate_count},包含侵权词:{qq_count},无运费险:{no_yun_fei_count},标题不含关键词：{no_contain_count},无销量:{no_sale_count},销量不合格:{sale_no2000_count},公司昵称不合格:{shop_name_not_count},价格不合格:{price_not_count},评分不合格:{pf_not_count},已存在忽略:{ig_n}')
    return suc

def parse_1688_des_html(cot):
    
    mat_des=re.search(r'"result":(.*?)"module":',cot,re.S)
    if mat_des:
        json_txt= mat_des.group(1)
        return dirtyjson.loads(json_txt)


def parse_1688_des_html2(cot):
    
    mat_des=re.search(r'window\.__GLOBAL_DADA.*?window\.__INIT_DATA\s?=(.*)</script>',cot,re.S)
    if mat_des:
        json_txt= mat_des.group(1)
        return dirtyjson.loads(json_txt)


def get_1688_description(page,url):
    
    cot=playwright_make_request_for1688(page,url,method='GET')
    pat_description=re.compile(r'var offer_details\=(.*);',re.S)
    mat=pat_description.match(cot)
    if mat:
        js_data=dirtyjson.loads(mat.group(1))
        return js_data['content']


def wait_1688_des_html(page:Page,basic_info,url, timeout: float = 60):
    bid=basic_info['BrowserID']
    dpName=basic_info['DpName']
    def patten_des(res):
        acc_url='https://gobuy.1688.com/pc/base/account?'
        if acc_url in res.url:
            if 'userId' not in res.text():
                print('未登录账号')
                raise LoginError
            else:
                print('账号已登录')
        
        if 'notfound&theme=index' in res.url:
            return True


        if url in res.url and res.status == 200:
            try:
                cot=res.text()
                # with open(f'阿里巴巴测试.html','w',encoding='utf=8') as f:
                #     f.write(cot)
                res_des=parse_1688_des_html(cot)

                if res_des:
                    return True
                else:
                    res_des2=parse_1688_des_html2(cot)
                    if res_des2:
                        print(f'链接《{url}》,第二种模版解析')
                        return True
                    
                    if '商品已下架' in cot:
                        print(f'链接《{url}》,产品下架')
                        return True
                    
                    return False
                    
            except:
                return False
        else:
            return False
        
        
    try:
        
        with page.expect_event("response", lambda res :patten_des(res),timeout=timeout*1000) as resp_info:
            page.goto(url,timeout=180000)
            time.sleep(3)
            slider_watcher(page)
        res = resp_info.value
        cot=res.text()
        
        js_data=parse_1688_des_html(cot)
        if js_data:
            buyer_id=js_data['global']['globalData']['parametersMap']['loginId']
            if buyer_id:
                # print(f'采集成功,买家id:{buyer_id}')
                # with open('采集成功.json','w',encoding='utf-8') as f:
                #     json.dump(js_data,f,ensure_ascii=False)
                return {'code':0,'data':js_data}
            else:
                return {'code': -2, 'msg': '买家号未登录'}

        else:
            js_data2=parse_1688_des_html2(cot)
            if js_data2:
                return {'code':0,'data':js_data2}
            if '商品已下架' in cot:
                return {'code':404,'msg': '商品下架'}
            elif 'notfound&theme=index' in page.url:
                return {'code':404,'msg': '商品找不到'}
            return {'code':-5,'msg': '解析错误'}

    except LoginError as le:
        logging.info(f"店铺:《{dpName}》,{bid},登录失败：{le}")
        return {'code': -2, 'msg': str(le)}

    except PlaywrightTimeout as te:
        logging.info(f"店铺:《{dpName}》,{bid},接口超时：{te}")
        return {'code': -3, 'msg': '接口超时'}

    except Exception as e:
        #traceback.print_exc()
        logging.info(f'店铺:《{dpName}》,{bid},未知接口错误 => {e}')
        return {'code': -4, 'msg': '接口错误'}


def save_1688_pro_deail(pdata,pitem):

    pid,purl,itemid=pitem
    is_update_pro=False
    is_update_sku=False
    try:
        pdata_item=pdata['data']
        dataJson=pdata_item['Root']['fields']['dataJson']
        gallery=pdata_item['gallery']['fields']
        offerDetail=pdata['global']['globalData']['model']['offerDetail']
        imgs=json.dumps(gallery['offerImgList'])
        video_info_list=json.dumps(gallery['video']) if gallery.get('video') else None
        attributes=json.dumps(offerDetail['featureAttributes'])
        tier_variations=dataJson['skuModel'].get('skuProps')
        offerminprice=pdata_item['mainPrice']['fields']['finalPriceModel']['tradeWithoutPromotion']['offerMinPrice']
        begin_num=dataJson.get('orderParamModel',{}).get('orderParam',{}).get('beginNum')
        print(f'采集起批数:{begin_num}')
        sku_info={
            'sku1_imgs':{},
            'sku2_imgs':{}
        }

        if tier_variations:
            for j in range(len(tier_variations)):
                sku_values=tier_variations[j]['value']
                for sku_value in sku_values:
                    sku_attr=sku_value.get('name')
                    sku_img=sku_value.get('imageUrl')
                    if sku_img:
                        sku_info[f'sku{j+1}_imgs'][sku_attr]=sku_img
        pdes=None

        #补全表《ShopeePHADPro》信息
        models=dataJson['skuModel']['skuInfoMap']
        historical_sold=dataJson['tempModel'].get('saledCount')
        etra_dict={
            'catId':dataJson['offerBaseInfo'].get('catId'),
            'images':imgs,
            'video_info_list':video_info_list,
            'tier_variations':json.dumps(tier_variations) if tier_variations else None,
            'attributes':attributes,
            'models':json.dumps(models),
            'min_order_qty':begin_num,
            'pdec':pdes
        }
        if historical_sold:
            etra_dict['historical_sold']=historical_sold

        if (tier_variations and len(tier_variations)<2) or not tier_variations:
            etra_dict['sku2_name']=None
        zds=list(etra_dict.keys())
        paras=list(etra_dict.values())
        zdstr=','.join([f'{zd}=?' for zd in zds])
        aff_pro=tms.ExecNoQuery(f'update S1688Pro set {zdstr} where id=?',paras+[pid])
        if aff_pro:
            is_update_pro=True
        logging.info(f'itemid:{itemid},补全信息状态:{aff_pro}')
        #补全SKU信息
        pskus=[]
        now_time=datetime.datetime.now()
        aff_sku=0
        if tier_variations:
            for mod_k,mod_v in models.items():
                skuid=str(mod_v['skuId'])
                sku_price=mod_v.get('discountPrice')
                if not sku_price:
                    sku_price=offerminprice
                
                sku_text=mod_v['specAttrs']
                sku_sold=mod_v['saleCount']
                skuddd={
                    'sku1':None,
                    'sku2':None,
                    'sku1_img':None,
                    'sku2_img':None
                }
                skuspilt=sku_text.split('&gt;')

                for skj in range(len(skuspilt)):
                    skuddd[f'sku{skj+1}']=skuspilt[skj]
                    skuddd[f'sku{skj+1}_img']=sku_info[f'sku{skj+1}_imgs'].get(skuspilt[skj])

                psku_dict={
                    'itemid':itemid,
                    'skuid':skuid,
                    'sku_text':sku_text,
                    'sku1':skuddd['sku1'],
                    'sku2':skuddd['sku2'],
                    'sku1_img':skuddd['sku1_img'],
                    'sku2_img':skuddd['sku2_img'],
                    'sku_price':float(sku_price),
                    'sku_sold':sku_sold,
                    'from_pid':pid,
                    'update_time':now_time,
                    'upload_time':now_time
                }
                pskus.append(psku_dict)
                zdstr2=','.join(psku_dict.keys())
                paras2=list(psku_dict.values())
                wstr=','.join(['?' for _ in paras2])
                rs_sku=tms.ExecQuery('select count(*) from S1688ProSKU where skuid=?',(skuid,))
                if rs_sku[0][0]==0:
                    aff_sku+=tms.ExecNoQuery(f'insert into S1688ProSKU({zdstr2}) values({wstr})',paras2)
                else:
                    aff_sku+=1
        
        else:
            models={'sku1':'One Style'}
            psku_dict={
                'itemid':itemid,
                'skuid':itemid,
                'sku_text':'One Style',
                'sku1':'One Style',
                'sku1_img':gallery['offerImgList'][0],
                'sku_price':float(offerminprice),
                'from_pid':pid,
                'update_time':now_time,
                'upload_time':now_time
            }
            pskus.append(psku_dict)
            zdstr2=','.join(psku_dict.keys())
            paras2=list(psku_dict.values())
            wstr=','.join(['?' for _ in paras2])
            rs_sku=tms.ExecQuery('select count(*) from S1688ProSKU where skuid=?',(itemid,))
            if rs_sku[0][0]==0:
                aff_sku+=tms.ExecNoQuery(f'insert into S1688ProSKU({zdstr2}) values({wstr})',paras2)
            else:
                aff_sku+=1
        logging.info(f'itemid:{itemid},共有{len(models.keys())}个sku,存入 {aff_sku} 个')

        if aff_sku:
            is_update_sku=True
        
        return is_update_pro and is_update_sku
    
    except Exception as e:

        traceback.print_exc()
        logging.error(f'json解析错误 =>{e}')
        return 0


def save_1688_pro_deail2(pdata,pitem):

    pid,purl,itemid=pitem
    is_update_pro=False
    is_update_sku=False
    try:
        pdata_item=pdata['globalData']
        img_list= [img_dict['fullPathImageURI'] for img_dict in pdata_item['images']]
        imgs=json.dumps(img_list)
        video_info_list=None
        attributes=None
        tier_variations=pdata_item['skuModel'].get('skuProps')
        offerminprice=pdata_item['orderParamModel']['orderParam']['skuParam']['skuRangePrices'][0]['price']
        begin_num=pdata_item['orderParamModel']['orderParam'].get('beginNum')
        print(f'采集起批数:{begin_num}')
        p_ali_dict=pdata['data']
        is_sx=0
        for kk,alivv in p_ali_dict.items():
            if not alivv.get('componentType'):
                continue
            if alivv['componentType']=='@ali/tdmod-od-pc-attribute-new':
                is_sx=len(alivv['data'])
                attributes=json.dumps(alivv['data'],ensure_ascii=False)
            if alivv['componentType']=='@ali/tdmod-od-pc-attribute':
                is_sx=len(alivv['data'])
                attributes=json.dumps(alivv['data'],ensure_ascii=False)
            
            if alivv['componentType']=='@ali/tdmod-pc-od-main-pic':
                video_info_list=json.dumps(alivv['data']['video'],ensure_ascii=False)

        sku_info={
            'sku1_imgs':{},
            'sku2_imgs':{}
        }

        if tier_variations:
            for j in range(len(tier_variations)):
                sku_values=tier_variations[j]['value']
                for sku_value in sku_values:
                    sku_attr=sku_value.get('name')
                    sku_img=sku_value.get('imageUrl')
                    if sku_img:
                        sku_info[f'sku{j+1}_imgs'][sku_attr]=sku_img

        pdes=None

        #补全表《ShopeePHADPro》信息
        models=pdata_item['skuModel']['skuInfoMap']
        catId=pdata_item['offerBaseInfo'].get('catId')
        if not catId:
            catId=pdata_item['tempModel']['postCategoryId']

        etra_dict={
            'catId':catId,
            'historical_sold':pdata_item['orderParamModel']['orderParam']['saledCount'],
            'images':imgs,
            'video_info_list':video_info_list,
            'tier_variations':json.dumps(tier_variations),
            'attributes':attributes,
            'models':json.dumps(models),
            'min_order_qty':begin_num,
            'pdec':pdes
        }
        if not tier_variations or (tier_variations and len(tier_variations))<2:
            etra_dict['sku2_name']=None
        zds=list(etra_dict.keys())
        paras=list(etra_dict.values())
        zdstr=','.join([f'{zd}=?' for zd in zds])
        aff_pro=tms.ExecNoQuery(f'update S1688Pro set {zdstr} where id=?',paras+[pid])
        if aff_pro:
            is_update_pro=True
        
        logging.info(f'itemid:{itemid},补全信息状态:{aff_pro},属性值项数:{is_sx}')
        #补全SKU信息
        pskus=[]
        now_time=datetime.datetime.now()
        aff_sku=0
        if tier_variations:
            for mod_k,mod_v in models.items():
                skuid=str(mod_v['skuId'])
                sku_price=mod_v.get('discountPrice')
                if not sku_price:
                    sku_price=offerminprice
                
                sku_text=mod_v['specAttrs']
                sku_sold=mod_v['saleCount']
                isPromotionSku=mod_v['isPromotionSku']

                skuddd={
                    'sku1':None,
                    'sku2':None,
                    'sku1_img':None,
                    'sku2_img':None
                }
                skuspilt=sku_text.split('&gt;')

                for skj in range(len(skuspilt)):
                    skuddd[f'sku{skj+1}']=skuspilt[skj]
                    skuddd[f'sku{skj+1}_img']=sku_info[f'sku{skj+1}_imgs'].get(skuspilt[skj])

                psku_dict={
                    'itemid':itemid,
                    'skuid':skuid,
                    'sku_text':sku_text,
                    'sku1':skuddd['sku1'],
                    'sku2':skuddd['sku2'],
                    'sku1_img':skuddd['sku1_img'],
                    'sku2_img':skuddd['sku2_img'],
                    'sku_price':float(sku_price),
                    'sku_sold':sku_sold,
                    'from_pid':pid,
                    'update_time':now_time,
                    'upload_time':now_time
                }
                pskus.append(psku_dict)
                zdstr2=','.join(psku_dict.keys())
                paras2=list(psku_dict.values())
                wstr=','.join(['?' for _ in paras2])
                rs_sku=tms.ExecQuery('select count(*) from S1688ProSKU where skuid=?',(skuid,))
                if rs_sku[0][0]==0:
                    aff_sku+=tms.ExecNoQuery(f'insert into S1688ProSKU({zdstr2}) values({wstr})',paras2)
                else:
                    aff_sku+=1
        else:
            models={'sku1':'One Style'}
            psku_dict={
                'itemid':itemid,
                'skuid':itemid,
                'sku_text':'One Style',
                'sku1':'One Style',
                'sku1_img':img_list[0],
                'sku_price':float(offerminprice),
                'from_pid':pid,
                'update_time':now_time,
                'upload_time':now_time
            }
            pskus.append(psku_dict)
            zdstr2=','.join(psku_dict.keys())
            paras2=list(psku_dict.values())
            wstr=','.join(['?' for _ in paras2])
            rs_sku=tms.ExecQuery('select count(*) from S1688ProSKU where skuid=?',(itemid,))
            if rs_sku[0][0]==0:
                aff_sku+=tms.ExecNoQuery(f'insert into S1688ProSKU({zdstr2}) values({wstr})',paras2)
            else:
                aff_sku+=1
        logging.info(f'itemid:{itemid},共有{len(models.keys())}个sku,存入 {aff_sku} 个')

        if aff_sku:
            is_update_sku=True
        
        return is_update_pro and is_update_sku
    
    except Exception as e:

        traceback.print_exc()
        logging.error(f'json解析错误 =>{e}')
        return 0

def slider_watcher(page:Page):
    is_send_email=False
    while True:
        try:
            if page.locator("#nc_1_n1z").is_visible():
                print("✅ 检测到滑块，开始滑动")
                # if not is_send_email:
                #     is_send_email=send_email_qq(
                #                                 '1126642524@qq.com',
                #                                 '阿里巴巴滑块通知',
                #                                 '采购账号出现滑块'
                #                                 )
                slider = page.locator("#nc_1_n1z")
                
                # 获取滑块轨道宽度
                track_width = page.eval_on_selector("#nc_1__scale_text", "el => el.offsetWidth")
                total_distance = track_width +10# 安全余量

                print(f"滑块轨道宽度: {track_width}px，拟滑动: {total_distance}px")

                human_like_mouse_move(page, slider, total_distance)

                # 检查验证是否通过
                time.sleep(60)
                if '_____tmd_____/punish' not in page.url:
                    print("🎉 滑块验证成功！")
                    return
                else:
                    print("❌ 验证失败或需要重试")
                    page.reload()
                    time.sleep(10)
                
            else:
                return


        
        except Exception as e:
            pass
        time.sleep(3)

def human_like_mouse_move(page: Page, element, distance):
    import math

    box = element.bounding_box()
    start_x = box['x'] + box['width'] / 2
    start_y = box['y'] + box['height'] / 2

    page.mouse.move(start_x, start_y)
    page.mouse.down()

    current = 0
    while current < distance:
        # 根据剩余距离调整步长，例如先大后小
        step = random.uniform(5, 12) if current < distance*0.6 else random.uniform(1, 5)
        if current + step > distance: 
            step = distance - current
        # 每次滑动时垂直方向加随机抖动
        jitter_y = start_y + random.uniform(-2, 2)
        page.mouse.move(start_x + current + step, jitter_y, steps=5)
        current += step
        time.sleep(random.uniform(0.01, 0.05))
        # 可选：在中途偶尔暂停以模拟犹豫
        if random.random() < 0.1 and current < distance*0.9:
            time.sleep(random.uniform(0.1, 0.2))
        
    page.mouse.up()
    time.sleep(random.uniform(0.1, 0.2))
    page.click(delay=200)

def wait_1688_order_url(page:Page,basic_info,url,cpp,timeout: float = 60):
    bid=basic_info['BrowserID']
    dpName=basic_info['DpName']
    def tppp1(res):
        order_json_url='https://h5api.m.1688.com/h5/mtop.1688.trading.dataline.service/1.0/'
        if order_json_url in res.url:
            try:
                jsdata=res.json()
                order_json_str=jsdata['data']['data'].get('result')
                if order_json_str:
                    order_json=json.loads(order_json_str)

                    order_list=order_json['data']['data']
                    print('成功获取订单JSON')
                    return True
            except:
                return False
        else:
            return False
        
    order_sku_map={}
    try:
        
        with page.expect_event("response", lambda res :tppp1(res),timeout=timeout*1000) as resp_info:

           
            if cpp==1:
                logging.info(f'店铺:《{dpName}》,{bid},第一步,跳转到订单页...')
                page.goto(url,timeout=180000)
            else:
                logging.info(f'店铺:《{dpName}》,{bid},第一步,点击下一页按钮...')
                el_order_top=page.locator('app-root[data-tracker="app-root"] >> order-list >> order-list-wait-buyer-pay-header >> div.order-list-wait-buyer-pay-header ')
                el_next_p=el_order_top.locator('div.right q-pagination >> lu-pagination >> fieldset#wrap button#right')
                el_next_p.click(delay=200)
            time.sleep(human_wait(min_seconds=3))
            #page.wait_for_load_state('networkidle')
            cookies=page.context.cookies(page.url)
            is_logged_in = any(
                c['name'] == 'cookie2' or  
                (c['name'] == '__cn_logon__' and c['value'] == 'true') or 
                c['name'] == '_tb_token_'
                for c in cookies
            )
            
            if not is_logged_in:
                logging.info(f'店铺:《{dpName}》,{bid},账户未登录')
                raise LoginError
            logging.info(f'店铺:《{dpName}》,{bid},账户登录状态:{is_logged_in}')
            time.sleep(1)
            #slider_watcher(page)
        res = resp_info.value
        js_data=res.json()
        order_json_str=js_data['data']['data'].get('result')
        order_json=json.loads(order_json_str)
        order_list=order_json['data']['data']
        max_page=order_json['data']['pages']
        for cur_order in order_list:
            order_id=cur_order['id']
            orderEntries=cur_order['orderEntries']
            memo_remark=cur_order.get('memo',{}).get('remark')
            for orderEntrie in orderEntries:
                pid=orderEntrie['sourceId']
                skuid=orderEntrie.get('skuId')
                if not skuid:
                    skuid=pid
                cur_sku_dict={
                    'pname':orderEntrie['productName'],
                    'specId':orderEntrie.get('specId'),
                    'sku_img':orderEntrie.get('mainSummImageUrl'),
                    'memo_remark':memo_remark,
                    'skuid':skuid,
                    'sku1':None,
                    'sku2':None
                }
                specInfo=orderEntrie.get('specInfo')
                if specInfo and specInfo.get('specItems'):
                    for i,specItem in enumerate(specInfo.get('specItems'),1):
                        cur_sku_dict[f'sku{i}']=specItem['specValue']
                order_sku_map[(order_id,skuid)]=cur_sku_dict


        logging.info(f'店铺:《{dpName}》,{bid},第二步,点击全选按钮...')
        while True:
            try:
                el_order_top=page.locator('app-root[data-tracker="app-root"] >> order-list >> order-list-wait-buyer-pay-header >> div.order-list-wait-buyer-pay-header')
                el_check_all=el_order_top.locator('div.left q-checkbox')
                el_check_all.click(delay=200)
                time.sleep(human_wait(max_seconds=3))
                checked=el_check_all.get_attribute('checked')
                if checked=='true':
                    logging.info(f'店铺:《{dpName}》,{bid},订单已全部选中')
                    break
            except Exception as e:
                print(e)
            time.sleep(3)
        time.sleep(human_wait())
        logging.info(f'店铺:《{dpName}》,{bid},第三步,点击导出所选订单...')
        
        while True:
            try:
                el_btn_xl=el_order_top.locator('div.left q-button').nth(0)
                el_btn_xl.click(delay=200)
                time.sleep(human_wait(max_seconds=3))
                el_q_dialog=el_order_top.locator('div.left dialog-export >> q-dialog[data-tracker="order-list.order-list-wait-buyer-pay-header.dialog-export.q-dialog"]')
                if el_q_dialog.get_attribute('visible')=='true':
                    logging.info(f'店铺:《{dpName}》,{bid},已点击导出所选订单')
                    break
            except Exception as e:
                print(e)

            time.sleep(3)
        time.sleep(human_wait())
        jtq_create_new=page.expect_response(lambda r: 'mtop.1688.trading.dataline.service' in r.url,timeout=10000)
        logging.info(f'店铺:《{dpName}》,{bid},第四步,点击生成新报表')
        while True:
            try:
                el_option=el_q_dialog.locator('div[slot="footer"] q-button').nth(1)
                if el_option.count()==0:
                    el_option=page.locator('xpath=//q-dialog[@data-tracker="q-dialog" and @visible="true"]/div[@slot="footer"]/q-button[2]')
                if el_option.count()>0:
                    el_option.click(delay=200)
                    time.sleep(5)
                try:
                    with jtq_create_new as res_jt_cre:
                        resp_jt_cre=res_jt_cre.value
                        jsdata_jt_cre=resp_jt_cre.json()
                        msg_000=jsdata_jt_cre.get('ret',[''])
                        if '您今天导出订单的次数已经达到' in msg_000[0]:
                            return {'code':-1,'msg':'您今天导出订单的次数已经达到上限'}

                except Exception as e:
                    print(e)
                    
            except Exception as e:
                print(e)
            try:
                el_exp_dialog=el_order_top.locator('div.left dialog-export >> dialog-export-record >> q-dialog[data-tracker="order-list.order-list-wait-buyer-pay-header.dialog-export.dialog-export-record.q-dialog"]')
                if el_exp_dialog.get_attribute('visible')=='true':
                    logging.info(f'店铺:《{dpName}》,{bid},已点击生成新报表')
                    break

            except Exception as e:
                print(e)

            time.sleep(3)
        time.sleep(human_wait())

        logging.info(f'店铺:《{dpName}》,{bid},第五步,等待30秒然后点击刷新按钮')
        time.sleep(20)
        def tppp2(res):
            api_path_url='https://h5api.m.1688.com/h5/mtop.1688.trading.dataline.service/1.0/'
            if api_path_url in res.url:
                try:
                    jsdata=res.json()
                    ljson=jsdata['data']['data']
                    if ljson and ljson[0].get('exportStatus'):
                        print('成功获取导出列表')
                        return True
                    else:
                        return False
                except:
                    print(jsdata)
                    return False
            else:
                return False
        while True:
            with page.expect_event("response", lambda res :tppp2(res),timeout=timeout*1000) as resp_info:
                try:
                    
                    el_btn_flash=el_order_top.locator('div[slot="footer"][class="export-record-footer"] > q-button[modal-component="true"]')
                    #print(f'1刷新按钮数量:{el_btn_flash.count()}')
                    if el_btn_flash.count()==0:
                        el_btn_flash=page.locator('xpath=//q-dialog[@data-tracker="q-dialog_1" and @visible="true"]/div[@slot="footer"]/q-button[1]')
                    #print(f'3刷新按钮数量2:{el_btn_flash.count()}')
                    if el_btn_flash.count()>0:
                        el_btn_flash.click(delay=200)
                        time.sleep(human_wait(max_seconds=3))
                        logging.info(f'店铺:《{dpName}》,{bid},已点击刷新按钮')
                    
                except Exception as e:
                    print(e)
                time.sleep(3)
            
            time.sleep(human_wait())
            res = resp_info.value
            js_data=res.json()
            exsjson=js_data['data']['data'][0]
            print(f'店铺:《{dpName}》,{bid},当前导出状态:{exsjson["exportStatus"]}')
            if exsjson['exportStatus']=='SUCCESS':
                downloadUrl=exsjson['path']
                print(f'店铺:《{dpName}》,{bid},成功获取excel下载地址:{downloadUrl}')
                break
            else:
                time.sleep(10)
        el_close=el_order_top.locator('div.left dialog-export >> dialog-export-record >> q-dialog[data-tracker="order-list.order-list-wait-buyer-pay-header.dialog-export.dialog-export-record.q-dialog"] >> div.q-dialog-close')
        if el_close.count()==0:
            el_close=page.locator('q-dialog[data-tracker="q-dialog_1"][visible="true"]>> div.q-dialog-close')
        logging.info(f'店铺:《{dpName}》,{bid},点击关闭列表按钮{el_close.count()}')
        el_close.click(delay=200)

        return {'code':0,'data':downloadUrl,'order_sku_map':order_sku_map,'max_page':max_page,}

    except LoginError as le:

        return {'code':-1,'msg':'登录失效'}

    except PlaywrightTimeout as te:
        logging.info(f"店铺:《{dpName}》,{bid},接口超时：{te}")

        return {'code': -3, 'msg': '接口超时'}

    except Exception as e:
        #traceback.print_exc()
        logging.info(f'店铺:《{dpName}》,{bid},未知接口错误 => {e}')
        return {'code': -4, 'msg': '接口错误'}

def save_allzd_1688_pro_deail(pdata,pitem,only_sku=False):

    itemid=pitem[0]
    is_update_pro=False
    is_update_sku=False
    try:

        pdata_item=pdata['data']
        dataJson=pdata_item['Root']['fields']['dataJson']
        gallery=pdata_item['gallery']['fields']
        offerDetail=pdata['global']['globalData']['model']['offerDetail']
        imgs=json.dumps(gallery['offerImgList'])
        video_info_list=json.dumps(gallery['video']) if gallery.get('video') else None
        attributes=json.dumps(offerDetail['featureAttributes'])
        tier_variations=dataJson['skuModel'].get('skuProps')
        offerminprice=pdata_item['mainPrice']['fields']['finalPriceModel']['tradeWithoutPromotion']['offerMinPrice']
        pname=dataJson['tempModel']['offerTitle']
        shop_url=dataJson['tempModel']['winportUrl']
        shop_name=dataJson['tempModel']['companyName']
        shopid=dataJson['tempModel']['sellerMemberId']
        sku_info={
            'sku1_imgs':{},
            'sku2_imgs':{}
        }

        if tier_variations:
            for j in range(len(tier_variations)):
                sku_values=tier_variations[j]['value']
                for sku_value in sku_values:
                    sku_attr=sku_value.get('name')
                    sku_img=sku_value.get('imageUrl')
                    if sku_img:
                        sku_info[f'sku{j+1}_imgs'][sku_attr]=sku_img
        pdes=None

        #补全表《ShopeePHADPro》信息
        models=dataJson['skuModel']['skuInfoMap']
        historical_sold=dataJson['tempModel'].get('saledCount')
        etra_dict={
            'url':f'https://detail.1688.com/offer/{itemid}.html',
            'itemid':itemid,
            'shopid':shopid,
            'shop_name':shop_name,
            'shop_url':shop_url,
            'pname':clean_html(pname),
            'random_code':get_sjm(),
            'image':gallery['offerImgList'][0],
            'currency':'CNY',
            'price':offerminprice,
            'shop_location':'义乌',
            'bookedCount':historical_sold,
            'shop_year':5,
            'rating_star':4,
            'sku1_name':'color',
            'sku2_name':'size',
            'from_keywords':'',
            'json_type':'OFFER',
            'sale_text':f'{historical_sold}',
            'return_rate':0.3,
            'historical_sold':historical_sold,
            'emarket_json':'["shopee泰国","shopee马来","shopee印尼"]',
            'keyword_type':'ATC优秀',
            'keword_gender':1,
            'ship_tag':'明天达',
            'catId':dataJson['offerBaseInfo'].get('catId'),
            'images':imgs,
            'video_info_list':video_info_list,
            'tier_variations':json.dumps(tier_variations) if tier_variations else None,
            'attributes':attributes,
            'models':json.dumps(models),
            'pdec':pdes
        }
        if historical_sold:
            etra_dict['historical_sold']=historical_sold

        if (tier_variations and len(tier_variations)<2) or not tier_variations:
            etra_dict['sku2_name']=None
        
        if not only_sku:
            zds=list(etra_dict.keys())
            paras=list(etra_dict.values())
            aff_pro=tms.merge_into('S1688Pro',zds,paras,['itemid'],True)
            if aff_pro:
                is_update_pro=True
            logging.info(f'itemid:{itemid},采集状态:{aff_pro}')
        else:
            is_update_pro=True
        #补全SKU信息
        pskus=[]
        now_time=datetime.datetime.now()
        aff_sku=0
        if tier_variations:
            for mod_k,mod_v in models.items():
                skuid=str(mod_v['skuId'])
                sku_price=mod_v.get('discountPrice')
                if not sku_price:
                    sku_price=offerminprice
                
                sku_text=mod_v['specAttrs']
                sku_sold=mod_v['saleCount']
                skuddd={
                    'sku1':None,
                    'sku2':None,
                    'sku1_img':None,
                    'sku2_img':None
                }
                skuspilt=sku_text.split('&gt;')

                for skj in range(len(skuspilt)):
                    skuddd[f'sku{skj+1}']=skuspilt[skj]
                    skuddd[f'sku{skj+1}_img']=sku_info[f'sku{skj+1}_imgs'].get(skuspilt[skj])

                psku_dict={
                    'itemid':itemid,
                    'skuid':skuid,
                    'sku_text':sku_text,
                    'sku1':skuddd['sku1'],
                    'sku2':skuddd['sku2'],
                    'sku1_img':skuddd['sku1_img'],
                    'sku2_img':skuddd['sku2_img'],
                    'sku_price':float(sku_price),
                    'sku_sold':sku_sold,
                    'update_time':now_time,
                    'upload_time':now_time
                }
                pskus.append(psku_dict)
                zdstr2=','.join(psku_dict.keys())
                paras2=list(psku_dict.values())
                wstr=','.join(['?' for _ in paras2])
                rs_sku=tms.ExecQuery('select count(*) from S1688ProSKU where itemid=? and skuid=?',(itemid,skuid))
                if rs_sku[0][0]==0:
                    aff_sku+=tms.ExecNoQuery(f'insert into S1688ProSKU({zdstr2}) values({wstr})',paras2)
                else:
                    aff_sku+=1
        
        else:
            models={'sku1':'One Style'}
            psku_dict={
                'itemid':itemid,
                'skuid':itemid,
                'sku_text':'One Style',
                'sku1':'One Style',
                'sku1_img':gallery['offerImgList'][0],
                'sku_price':float(offerminprice),
                'update_time':now_time,
                'upload_time':now_time
            }
            pskus.append(psku_dict)
            zdstr2=','.join(psku_dict.keys())
            paras2=list(psku_dict.values())
            wstr=','.join(['?' for _ in paras2])
            rs_sku=tms.ExecQuery('select count(*) from S1688ProSKU where skuid=?',(itemid,))
            if rs_sku[0][0]==0:
                aff_sku+=tms.ExecNoQuery(f'insert into S1688ProSKU({zdstr2}) values({wstr})',paras2)
            else:
                aff_sku+=1
        logging.info(f'itemid:{itemid},共有{len(models.keys())}个sku,存入 {aff_sku} 个')

        if aff_sku:
            is_update_sku=True
        
        return is_update_pro and is_update_sku
    
    except Exception as e:

        traceback.print_exc()
        logging.error(f'json解析错误 =>{e}')
        return 0

def save_allzd_1688_pro_deail2(pdata,pitem,only_sku=False):

    itemid=pitem[0]
    is_update_pro=False
    is_update_sku=False
    try:
        pdata_item=pdata['globalData']
        img_list= [img_dict['fullPathImageURI'] for img_dict in pdata_item['images']]
        imgs=json.dumps(img_list)
        video_info_list=None
        attributes=None
        tier_variations=pdata_item['skuModel'].get('skuProps')
        offerminprice=pdata_item['orderParamModel']['orderParam']['skuParam']['skuRangePrices'][0]['price']
        pname=pdata_item['tempModel']['offerTitle']
        shop_name=pdata_item['tempModel']['companyName']
        shop_url=pdata_item['tempModel']['winportUrl']
        shopid=pdata_item['tempModel']['sellerMemberId']

        p_ali_dict=pdata['data']
        is_sx=0
        for kk,alivv in p_ali_dict.items():
            if not alivv.get('componentType'):
                continue
            if alivv['componentType']=='@ali/tdmod-od-pc-attribute-new':
                is_sx=len(alivv['data'])
                attributes=json.dumps(alivv['data'],ensure_ascii=False)
            if alivv['componentType']=='@ali/tdmod-od-pc-attribute':
                is_sx=len(alivv['data'])
                attributes=json.dumps(alivv['data'],ensure_ascii=False)
            
            if alivv['componentType']=='@ali/tdmod-pc-od-main-pic':
                video_info_list=json.dumps(alivv['data']['video'],ensure_ascii=False)

        sku_info={
            'sku1_imgs':{},
            'sku2_imgs':{}
        }

        if tier_variations:
            for j in range(len(tier_variations)):
                sku_values=tier_variations[j]['value']
                for sku_value in sku_values:
                    sku_attr=sku_value.get('name')
                    sku_img=sku_value.get('imageUrl')
                    if sku_img:
                        sku_info[f'sku{j+1}_imgs'][sku_attr]=sku_img

        pdes=None

        #补全表《ShopeePHADPro》信息
        models=pdata_item['skuModel']['skuInfoMap']
        catId=pdata_item['offerBaseInfo'].get('catId')
        if not catId:
            catId=pdata_item['tempModel']['postCategoryId']
        historical_sold=pdata_item['orderParamModel']['orderParam']['saledCount']
        etra_dict={
            'url':f'https://detail.1688.com/offer/{itemid}.html',
            'itemid':itemid,
            'shopid':shopid,
            'shop_name':shop_name,
            'shop_url':shop_url,
            'pname':clean_html(pname),
            'random_code':get_sjm(),
            'image':img_list[0],
            'currency':'CNY',
            'price':offerminprice,
            'shop_location':'义乌',
            'bookedCount':historical_sold,
            'shop_year':5,
            'rating_star':4,
            'sku1_name':'color',
            'sku2_name':'size',
            'from_keywords':'',
            'json_type':'OFFER',
            'sale_text':f'{historical_sold}',
            'return_rate':0.3,
            'historical_sold':historical_sold,
            'emarket_json':'["shopee泰国","shopee马来","shopee印尼"]',
            'keyword_type':'ATC优秀',
            'keword_gender':1,
            'ship_tag':'明天达',
            'catId':catId,
            'images':imgs,
            'video_info_list':video_info_list,
            'tier_variations':json.dumps(tier_variations),
            'attributes':attributes,
            'models':json.dumps(models),
            'pdec':pdes
        }
        if not tier_variations or (tier_variations and len(tier_variations))<2:
            etra_dict['sku2_name']=None
        if not only_sku:
            zds=list(etra_dict.keys())
            paras=list(etra_dict.values())
            aff_pro=tms.merge_into('S1688Pro',zds,paras,['itemid'],True)
            if aff_pro:
                is_update_pro=True
        else:
            is_update_pro=True
            
        logging.info(f'itemid:{itemid},补全信息状态:{aff_pro},属性值项数:{is_sx}')
        #补全SKU信息
        pskus=[]
        now_time=datetime.datetime.now()
        aff_sku=0
        if tier_variations:
            for mod_k,mod_v in models.items():
                skuid=str(mod_v['skuId'])
                sku_price=mod_v.get('discountPrice')
                if not sku_price:
                    sku_price=offerminprice
                
                sku_text=mod_v['specAttrs']
                sku_sold=mod_v['saleCount']
                isPromotionSku=mod_v['isPromotionSku']

                skuddd={
                    'sku1':None,
                    'sku2':None,
                    'sku1_img':None,
                    'sku2_img':None
                }
                skuspilt=sku_text.split('&gt;')

                for skj in range(len(skuspilt)):
                    skuddd[f'sku{skj+1}']=skuspilt[skj]
                    skuddd[f'sku{skj+1}_img']=sku_info[f'sku{skj+1}_imgs'].get(skuspilt[skj])

                psku_dict={
                    'itemid':itemid,
                    'skuid':skuid,
                    'sku_text':sku_text,
                    'sku1':skuddd['sku1'],
                    'sku2':skuddd['sku2'],
                    'sku1_img':skuddd['sku1_img'],
                    'sku2_img':skuddd['sku2_img'],
                    'sku_price':float(sku_price),
                    'sku_sold':sku_sold,
                    'update_time':now_time,
                    'upload_time':now_time
                }
                pskus.append(psku_dict)
                zdstr2=','.join(psku_dict.keys())
                paras2=list(psku_dict.values())
                wstr=','.join(['?' for _ in paras2])
                rs_sku=tms.ExecQuery('select count(*) from S1688ProSKU where itemid=? and skuid=?',(itemid,skuid))
                if rs_sku[0][0]==0:
                    aff_sku+=tms.ExecNoQuery(f'insert into S1688ProSKU({zdstr2}) values({wstr})',paras2)
                else:
                    aff_sku+=1
        else:
            models={'sku1':'One Style'}
            psku_dict={
                'itemid':itemid,
                'skuid':itemid,
                'sku_text':'One Style',
                'sku1':'One Style',
                'sku1_img':img_list[0],
                'sku_price':float(offerminprice),
                'update_time':now_time,
                'upload_time':now_time
            }
            pskus.append(psku_dict)
            zdstr2=','.join(psku_dict.keys())
            paras2=list(psku_dict.values())
            wstr=','.join(['?' for _ in paras2])
            rs_sku=tms.ExecQuery('select count(*) from S1688ProSKU where skuid=?',(itemid,))
            if rs_sku[0][0]==0:
                aff_sku+=tms.ExecNoQuery(f'insert into S1688ProSKU({zdstr2}) values({wstr})',paras2)
            else:
                aff_sku+=1
        logging.info(f'itemid:{itemid},共有{len(models.keys())}个sku,存入 {aff_sku} 个')

        if aff_sku:
            is_update_sku=True
        
        return is_update_pro and is_update_sku
    
    except Exception as e:

        traceback.print_exc()
        logging.error(f'json解析错误 =>{e}')
        return 0


def wait_1688_wuliu_url(page:Page,basic_info,oitem,url,timeout: float = 60):
    bid=basic_info['BrowserID']
    dpName=basic_info['DpName']
    oj,sjbm,yybm=oitem
    order_status=None
    err_msg=None
    def tppp3(res):
        nonlocal order_status,err_msg
        if 'api=mtop.1688.LogisticsTraceRenderMTopService.queryTraceWithRender' in res.url:
            try:
                jsdata=res.json()
                err_msg=jsdata['data'].get('errorMsg')
                if err_msg and ('目前暂未查询到该笔运单的物流跟踪信息' in err_msg):
                    print(f'店铺:{dpName},{bid},第 {oj} 个订单,{err_msg},跳出采集物流')
                    return True
                wu_list=jsdata['data']['model']['traceList']
                print(f'店铺:{dpName},{bid},第 {oj} 个订单,成功获取物流列表 {len(wu_list)} 个')
                return True
            except Exception as e:
                traceback.print_exc()
                return False
        elif 'api=mtop.1688.MtopOrderService.queryOrder' in res.url:
            try:
                jsdata=res.json()
                statusLabel=jsdata['data']['model']['statusLabel']
                if statusLabel in ['待付款','待发货','交易关闭']:
                    order_status=statusLabel
                    print(f'店铺:{dpName},{bid},第 {oj} 个订单,状态:{statusLabel},跳出采集物流')
                    return True
            except Exception as e:
                traceback.print_exc()
                return False
        else:
            return False
        
        

    try:
        
        with page.expect_event("response", lambda res :tppp3(res),timeout=timeout*1000) as resp_info:

        
            logging.info(f'店铺:《{dpName}》,{bid},第一步,跳转到订单详情页面...')
            page.goto(url,timeout=180000)
            time.sleep(human_wait(min_seconds=3))
            page.wait_for_load_state('networkidle') 
            cookies=page.context.cookies(page.url)
            is_logged_in = any(
                c['name'] == 'cookie2' or  
                (c['name'] == '__cn_logon__' and c['value'] == 'true') or 
                c['name'] == '_tb_token_'
                for c in cookies
            )
            
            if not is_logged_in:
                logging.info(f'店铺:《{dpName}》,{bid},账户未登录')
                raise LoginError
            logging.info(f'店铺:《{dpName}》,{bid},账户登录状态:{is_logged_in}')
            time.sleep(1)
            slider_watcher(page)
        
        if err_msg:
            return {'code':0,'data':[],'WuLiuZT':'','order_status':err_msg}

        if order_status:
            return {'code':0,'data':[],'WuLiuZT':'','order_status':order_status}
        res = resp_info.value
        js_data=res.json()

        traceList=js_data['data']['model']['traceList']
        tdes=js_data['data']['model']['logisticsTraceDetail']
        currentStatusDesc=tdes['currentStatusDesc']
        YunDanHao=tdes['mailNo']
        oid=tdes['tradeId']
        logging.info(f'店铺:{dpName},{bid},第 {oj} 个订单,《{oid}》,成功获取物流列表 {len(traceList)} 个,物流当前状态:{currentStatusDesc}')
        tdatas=[]
        for trace in traceList:

            tdata={
                'DingDanBianHao':oid,
                'YunDanHao':YunDanHao,
                'traceId':trace['traceId'],
                'acceptAddress':trace.get('acceptAddress'),
                'acceptTimeString':trace.get('acceptTimeString'),
                'action':trace.get('action'),
                'actionTime':trace.get('actionTime'),
                'areaCode':trace.get('areaCode'),
                'areaName':trace.get('areaName'),
                'facility':trace.get('facility'),
                'nodeStatus':trace.get('nodeStatus'),
                'remark':trace.get('remark'),
                'simplifiedStatus':trace.get('simplifiedStatus'),
                'simplifiedStatusDesc':trace.get('simplifiedStatusDesc'),
                'status':trace.get('status'),
                'WuLiuZT':currentStatusDesc,
                'bid':bid,
                'bname':dpName,
                'SJBM':sjbm,
                'YYBM':yybm
            }
            tdatas.append(tdata)

        return {'code':0,'data':tdatas,'WuLiuZT':currentStatusDesc,'order_status':order_status}

    except LoginError as le:

        return {'code':-1,'msg':'登录失效'}

    except PlaywrightTimeout as te:
        logging.info(f"店铺:《{dpName}》,{bid},接口超时：{te}")

        return {'code': -3, 'msg': '接口超时'}

    except Exception as e:
        traceback.print_exc()
        logging.info(f'店铺:《{dpName}》,{bid},未知接口错误 => {e}')
        return {'code': -4, 'msg': '接口错误'}


